/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package PuriON;

import java.io.*;
import java.net.InetAddress;

import java.awt.*;
import javax.media.*;
import javax.media.protocol.*;
import javax.media.format.*;
import javax.media.control.TrackControl;
import javax.media.control.QualityControl;
import javax.media.rtp.*;
import javax.media.rtp.rtcp.*;
import java.net.UnknownHostException;

/**
 *
 * @author hyukmini
 */
public class PURION_Transmit implements Runnable {

    private boolean iscreated = false;

    /* 전송 정보 */
    
    private String videoformat = null;
    private Integer framerate = null;
    private Float qualityrate = null;
    
    private int port;

    
    public RTPManager rtpMgrs[];           // 세션 메니져
    
    private MediaLocator locator = null;        // 미디어 로케이터
    public  Processor processor = null;         // 프로세서
    public  DataSource dataOutput = null;       // 데이터 소스
    private DataSource dataOutputCloneable = null;  // 데이터 소스 

    private PURION_Producer parent = null;
    // 데이터 저장을 위한 데이터 싱크 부분    private DataSink rtptransmitterVideo = null;
    
    /* 데이터의 위치(미디어 로케이터), 수식측 아이피 주소, RTP에 이용될 세션 포트 번호 */
    public PURION_Transmit(PURION_Producer _p, MediaLocator _l, String _f, Integer _fr, Float _qr) {
        
        this.parent = _p;
        this.locator = _l;
        this.videoformat = _f;
        this.framerate = _fr;
        this.qualityrate = _qr;
    }
    /* 전송을 시작하는 부분입니다. */
    public synchronized void startTransmit() {

        // 프로그램 실행시 지정된 Media Locator를 위한 processor를 생성하고 
        // processor가 출력이 JPEG/RTP 형식이 되도록 설정해 주는 부분입니다.
        this.createProcessor();

        // 프로그램 수행시 지정한 IP address, Port 번호를 가지고 
        // processor의 output을 전송하기 위한 RTP Session을 만듭니다. 
        this.createTransmitter();
        // 모든 생성 단계를 마쳤기 때문에 이 부분에서 전송을 시작합니다. 
        processor.start();

    }

    // 이미 전송이 진행 중이었다면 전송을 멈추는 부분입니다. 
    // 이 부분은 동기화가 이루어져야 하기 때문에 synchronized가 이용되었습니다.
    public void stopTransmit() {

        
        synchronized (this) {
            
                System.err.println("PURION_Transmit : RTP Transmit is stopped.");
//            if (processor != null) {
//                System.out.println("asdf");
                processor.stop();
                processor.deallocate(); 
                processor.close();
                processor = null;
                
                for (int i = 0; i < rtpMgrs.length; i++) {
                    rtpMgrs[i].dispose();
                }                
  //          }
        }
    }
    /* RTP Transmit를 위한 Processor 객체를 생성하고 포맷을 지정합니다. */
    private String createProcessor() {
	if (locator == null)
	    return "Locator is null";

	DataSource ds;
	try {
	    ds = Manager.createDataSource(locator);
	} catch (Exception e) {
	    return "Couldn't create DataSource";
	}
	// Try to create a processor to handle the input media locator
	try {
	    processor = Manager.createProcessor(ds);
	} catch (NoProcessorException npe) {
	    return "Couldn't create processor";
	} catch (IOException ioe) {
	    return "IOException creating processor";
	} 

	// Wait for it to configure
	boolean result = waitForState(processor, Processor.Configured);
	if (result == false)
	    return "Couldn't configure processor";

	// Get the tracks from the processor
	TrackControl [] tracks = processor.getTrackControls();

	// Do we have atleast one track?
	if (tracks == null || tracks.length < 1)
	    return "Couldn't find tracks in processor";

	// Set the output content descriptor to RAW_RTP
	// This will limit the supported formats reported from
	// Track.getSupportedFormats to only valid RTP formats.
	ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
	processor.setContentDescriptor(cd);

	Format supported[];
	boolean atLeastOneTrack = false;

	// Program the tracks.
	for (int i = 0; i < tracks.length; i++) {
	    Format format = tracks[i].getFormat();
	    if (tracks[i].isEnabled()) {

		supported = tracks[i].getSupportedFormats();

		// We've set the output content to the RAW_RTP.
		// So all the supported formats should work with RTP.
		// We'll just pick the first one.

		if (supported.length > 0) {
		    if (supported[0] instanceof VideoFormat) {
			// For video formats, we should double check the
                        // sizes since not all formats work in all sizes.
                        Dimension size = ((VideoFormat) format).getSize();
                        float frameRate = ((VideoFormat) format).getFrameRate();
                        System.out.println(size);
                        System.out.println(frameRate);
                        //172.17.20.223
                        VideoFormat jpegFormat = new VideoFormat(videoformat,
                                size, Format.NOT_SPECIFIED, Format.byteArray, frameRate);
                        /* 트랙의 포맷을 우리가 정한 포맷으로 지정 */
                        tracks[i].setFormat(jpegFormat);
		    }
                    else {
                        tracks[i].setFormat(supported[0]);
                    }
                    
		    System.err.println("Track " + i + " is set to transmit as:");
		    System.err.println("  " + tracks[i].getFormat());
		    atLeastOneTrack = true;
		} else
		    tracks[i].setEnabled(false);
	    } else
		tracks[i].setEnabled(false);
	}

	if (!atLeastOneTrack)
	    return "Couldn't set any of the tracks to a valid RTP format";

	// Realize the processor. This will internally create a flow
	// graph and attempt to create an output datasource for JPEG/RTP
	// audio frames.
	result = waitForState(processor, Controller.Realized);
        
        
	if (result == false)
	    return "Couldn't realize processor";

        setJPEGQuality(processor);
        
        // Get the output data source of the processor
	dataOutput = processor.getDataOutput();
        dataOutputCloneable = Manager.createCloneableDataSource(dataOutput); 

        iscreated = true;
	return null;
    }    

    private void createTransmitter() {

        /* 데이터 소스를 전송할 버퍼와 스트림을 생성합니다. */
        PushBufferDataSource pbds = (PushBufferDataSource) dataOutput;
        PushBufferStream pbss[] = pbds.getStreams();
        
        /* RTP 세션 메니져를 생성합니다. */
        rtpMgrs = new RTPManager[pbss.length];

        SessionAddress localAddr = null;
        port = 40000;
        SourceDescription srcDesList[];
        System.out.println("권혁민 : " + pbss.length);
        for (int i = 0; i < pbss.length; i++) {
            try {
                rtpMgrs[i] = RTPManager.newInstance();
                localAddr = new SessionAddress(InetAddress.getLocalHost(), port + 2 * i);
                rtpMgrs[i].initialize(localAddr);
            } catch (Exception e) {
                System.err.println("Exception in createTransmitter " + e);
                return;
            }
        }
    }

    // 주어진 상태를 인자로 주고, 해당 프로세서가 그 상태까지 도달하도록
    // 기다리게 해서 다른 작업이 원치 않는 단계에서 수행되지 않도록 blocking을 합니다.
    public synchronized boolean waitForState(Processor p, int state) {
        p.addControllerListener(new StateListener());
        failed = false;
        // processor에게 원하는 단계가 되도록 해당 메소드를 호출합니다.
        if (state == Processor.Configured) {
            System.err.println("PURION_Transmit : Processor is configured.");
            p.configure();
        } else if (state == Processor.Realized) {
            System.err.println("PURION_Transmit : Processor is realized.");
            p.realize();
        } else if(state == Processor.Started) {
            System.err.println("PURION_Transmit : Processor is started.");
            p.start();
        }else if(state == Processor.Unrealized) {
            System.out.println("TEST");
        }
            
        // 메소드 호출시에 성공을 알리는 이벤트를 얻을 때까지 기다리도록 합니다.
        // StateListener inner class를 참조하세요.
        while (p.getState() < state && !failed) {
            synchronized (getStateLock()) {
                try {
                    getStateLock().wait();
                } catch (InterruptedException ie) {
                    System.err.println(ie);
                    return false;
                }
            }
        }
        if (failed) {
            return false;
        } else {
            return true;
        }
    }
    // 이 부분에서는 RTP 전송과는 직접 관련이 없지만
    // processor의 state 변화를 핸들링하는 함수들로 구성되어 있습니다.
    private Integer stateLock = new Integer(0);
    private boolean failed = false;

    Integer getStateLock() {
        return stateLock;
    }

    void setFailed() {
        failed = true;
    }
    // ControllerListener를 상속받아 이벤트 처리를 하는 Inner class 입니다.
    class StateListener implements ControllerListener {

        public void controllerUpdate(ControllerEvent ce) {
            if (ce instanceof ControllerClosedEvent) {
                setFailed();
            }
            if (ce instanceof ControllerEvent) {
                synchronized (getStateLock()) {
                    getStateLock().notifyAll();
                }
            }
        }
    }
    private void setJPEGQuality(Player p) {

        Control cs[] = p.getControls();
        QualityControl qc = null;
        VideoFormat jpegFmt = new VideoFormat(videoformat);

        // Loop through the controls to find the Quality control for
        // the JPEG encoder.
        for (int i = 0; i < cs.length; i++) {

            if (cs[i] instanceof QualityControl &&
                    cs[i] instanceof Owned) {
                Object owner = ((Owned) cs[i]).getOwner();

                // Check to see if the owner is a Codec.
                // Then check for the output format.
                if (owner instanceof Codec) {
                    Format fmts[] = ((Codec) owner).getSupportedOutputFormats(null);
                    for (int j = 0; j < fmts.length; j++) {
                        if (fmts[j].matches(jpegFmt)) {
                            qc = (QualityControl) cs[i];
                            qc.setQuality(qualityrate);

                            //MessageBox.addMessage("Setting JPEG quality to " + val + " on " + qc);
                            break;
                        }
                    }
                }
                if (qc != null) {
                    break;
                }
            }
        }
    }
    
    public void run() {    
        try {
            startTransmit();
            System.out.println((int) (processor.getDuration().getSeconds()));
            Thread.sleep((int) (processor.getDuration().getSeconds()) * 1000);
            System.out.println("Ended");
            this.stopTransmit();
        } catch (InterruptedException ie) {
            System.err.println(ie);
        }
    }
}